
/* Copyright 1998 by the Massachusetts Institute of Technology.
 * Copyright (C) 2008-2013 by Daniel Stenberg
 *
 * Permission to use, copy, modify, and distribute this
 * software and its documentation for any purpose and without
 * fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting
 * documentation, and that the name of M.I.T. not be used in
 * advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 */

#include "ares_setup.h"

#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif

#include "ares.h"
#include "ares_data.h"
#include "ares_inet_net_pton.h"
#include "ares_private.h"

int ares_get_servers(ares_channel channel,
    struct ares_addr_node** servers)
{
    struct ares_addr_node* srvr_head = NULL;
    struct ares_addr_node* srvr_last = NULL;
    struct ares_addr_node* srvr_curr;
    int status = ARES_SUCCESS;
    int i;

    if (!channel)
        return ARES_ENODATA;

    for (i = 0; i < channel->nservers; i++) {
        /* Allocate storage for this server node appending it to the list */
        srvr_curr = ares_malloc_data(ARES_DATATYPE_ADDR_NODE);
        if (!srvr_curr) {
            status = ARES_ENOMEM;
            break;
        }
        if (srvr_last) {
            srvr_last->next = srvr_curr;
        } else {
            srvr_head = srvr_curr;
        }
        srvr_last = srvr_curr;

        /* Fill this server node data */
        srvr_curr->family = channel->servers[i].addr.family;
        if (srvr_curr->family == AF_INET)
            memcpy(&srvr_curr->addrV4, &channel->servers[i].addr.addrV4,
                sizeof(srvr_curr->addrV4));
        else
            memcpy(&srvr_curr->addrV6, &channel->servers[i].addr.addrV6,
                sizeof(srvr_curr->addrV6));
    }

    if (status != ARES_SUCCESS) {
        if (srvr_head) {
            ares_free_data(srvr_head);
            srvr_head = NULL;
        }
    }

    *servers = srvr_head;

    return status;
}

int ares_get_servers_ports(ares_channel channel,
    struct ares_addr_port_node** servers)
{
    struct ares_addr_port_node* srvr_head = NULL;
    struct ares_addr_port_node* srvr_last = NULL;
    struct ares_addr_port_node* srvr_curr;
    int status = ARES_SUCCESS;
    int i;

    if (!channel)
        return ARES_ENODATA;

    for (i = 0; i < channel->nservers; i++) {
        /* Allocate storage for this server node appending it to the list */
        srvr_curr = ares_malloc_data(ARES_DATATYPE_ADDR_PORT_NODE);
        if (!srvr_curr) {
            status = ARES_ENOMEM;
            break;
        }
        if (srvr_last) {
            srvr_last->next = srvr_curr;
        } else {
            srvr_head = srvr_curr;
        }
        srvr_last = srvr_curr;

        /* Fill this server node data */
        srvr_curr->family = channel->servers[i].addr.family;
        srvr_curr->udp_port = ntohs((unsigned short)channel->servers[i].addr.udp_port);
        srvr_curr->tcp_port = ntohs((unsigned short)channel->servers[i].addr.tcp_port);
        if (srvr_curr->family == AF_INET)
            memcpy(&srvr_curr->addrV4, &channel->servers[i].addr.addrV4,
                sizeof(srvr_curr->addrV4));
        else
            memcpy(&srvr_curr->addrV6, &channel->servers[i].addr.addrV6,
                sizeof(srvr_curr->addrV6));
    }

    if (status != ARES_SUCCESS) {
        if (srvr_head) {
            ares_free_data(srvr_head);
            srvr_head = NULL;
        }
    }

    *servers = srvr_head;

    return status;
}

int ares_set_servers(ares_channel channel,
    struct ares_addr_node* servers)
{
    struct ares_addr_node* srvr;
    int num_srvrs = 0;
    int i;

    if (ares_library_initialized() != ARES_SUCCESS)
        return ARES_ENOTINITIALIZED; /* LCOV_EXCL_LINE: n/a on non-WinSock */

    if (!channel)
        return ARES_ENODATA;

    ares__destroy_servers_state(channel);

    for (srvr = servers; srvr; srvr = srvr->next) {
        num_srvrs++;
    }

    if (num_srvrs > 0) {
        /* Allocate storage for servers state */
        channel->servers = ares_malloc(num_srvrs * sizeof(struct server_state));
        if (!channel->servers) {
            return ARES_ENOMEM;
        }
        channel->nservers = num_srvrs;
        /* Fill servers state address data */
        for (i = 0, srvr = servers; srvr; i++, srvr = srvr->next) {
            channel->servers[i].addr.family = srvr->family;
            channel->servers[i].addr.udp_port = 0;
            channel->servers[i].addr.tcp_port = 0;
            if (srvr->family == AF_INET)
                memcpy(&channel->servers[i].addr.addrV4, &srvr->addrV4,
                    sizeof(srvr->addrV4));
            else
                memcpy(&channel->servers[i].addr.addrV6, &srvr->addrV6,
                    sizeof(srvr->addrV6));
        }
        /* Initialize servers state remaining data */
        ares__init_servers_state(channel);
    }

    return ARES_SUCCESS;
}

int ares_set_servers_ports(ares_channel channel,
    struct ares_addr_port_node* servers)
{
    struct ares_addr_port_node* srvr;
    int num_srvrs = 0;
    int i;

    if (ares_library_initialized() != ARES_SUCCESS)
        return ARES_ENOTINITIALIZED; /* LCOV_EXCL_LINE: n/a on non-WinSock */

    if (!channel)
        return ARES_ENODATA;

    ares__destroy_servers_state(channel);

    for (srvr = servers; srvr; srvr = srvr->next) {
        num_srvrs++;
    }

    if (num_srvrs > 0) {
        /* Allocate storage for servers state */
        channel->servers = ares_malloc(num_srvrs * sizeof(struct server_state));
        if (!channel->servers) {
            return ARES_ENOMEM;
        }
        channel->nservers = num_srvrs;
        /* Fill servers state address data */
        for (i = 0, srvr = servers; srvr; i++, srvr = srvr->next) {
            channel->servers[i].addr.family = srvr->family;
            channel->servers[i].addr.udp_port = htons((unsigned short)srvr->udp_port);
            channel->servers[i].addr.tcp_port = htons((unsigned short)srvr->tcp_port);
            if (srvr->family == AF_INET)
                memcpy(&channel->servers[i].addr.addrV4, &srvr->addrV4,
                    sizeof(srvr->addrV4));
            else
                memcpy(&channel->servers[i].addr.addrV6, &srvr->addrV6,
                    sizeof(srvr->addrV6));
        }
        /* Initialize servers state remaining data */
        ares__init_servers_state(channel);
    }

    return ARES_SUCCESS;
}

/* Incomming string format: host[:port][,host[:port]]... */
/* IPv6 addresses with ports require square brackets [fe80::1%lo0]:53 */
static int set_servers_csv(ares_channel channel,
    const char* _csv, int use_port)
{
    size_t i;
    char* csv = NULL;
    char* ptr;
    char* start_host;
    int cc = 0;
    int rv = ARES_SUCCESS;
    struct ares_addr_port_node* servers = NULL;
    struct ares_addr_port_node* last = NULL;

    if (ares_library_initialized() != ARES_SUCCESS)
        return ARES_ENOTINITIALIZED; /* LCOV_EXCL_LINE: n/a on non-WinSock */

    if (!channel)
        return ARES_ENODATA;

    ares__destroy_servers_state(channel);

    i = strlen(_csv);
    if (i == 0)
        return ARES_SUCCESS; /* blank all servers */

    csv = ares_malloc(i + 2);
    if (!csv)
        return ARES_ENOMEM;

    strcpy(csv, _csv);
    if (csv[i - 1] != ',') { /* make parsing easier by ensuring ending ',' */
        csv[i] = ',';
        csv[i + 1] = 0;
    }

    start_host = csv;
    for (ptr = csv; *ptr; ptr++) {
        if (*ptr == ':') {
            /* count colons to determine if we have an IPv6 number or IPv4 with
         port */
            cc++;
        } else if (*ptr == '[') {
            /* move start_host if an open square bracket is found wrapping an IPv6
         address */
            start_host = ptr + 1;
        } else if (*ptr == ',') {
            char* pp = ptr - 1;
            char* p = ptr;
            int port = 0;
            struct in_addr in4;
            struct ares_in6_addr in6;
            struct ares_addr_port_node* s = NULL;

            *ptr = 0; /* null terminate host:port string */
            /* Got an entry..see if the port was specified. */
            if (cc > 0) {
                while (pp > start_host) {
                    /* a single close square bracket followed by a colon, ']:' indicates
             an IPv6 address with port */
                    if ((*pp == ']') && (*p == ':'))
                        break; /* found port */
                    /* a single colon, ':' indicates an IPv4 address with port */
                    if ((*pp == ':') && (cc == 1))
                        break; /* found port */
                    if (!(ISDIGIT(*pp) || (*pp == ':'))) {
                        /* Found end of digits before we found :, so wasn't a port */
                        /* must allow ':' for IPv6 case of ']:' indicates we found a port */
                        pp = p = ptr;
                        break;
                    }
                    pp--;
                    p--;
                }
                if ((pp != start_host) && ((pp + 1) < ptr)) {
                    /* Found it. Parse over the port number */
                    /* when an IPv6 address is wrapped with square brackets the port
             starts at pp + 2 */
                    if (*pp == ']')
                        p++; /* move p before ':' */
                    /* p will point to the start of the port */
                    port = (int)strtol(p, NULL, 10);
                    *pp = 0; /* null terminate host */
                }
            }
            /* resolve host, try ipv4 first, rslt is in network byte order */
            rv = ares_inet_pton(AF_INET, start_host, &in4);
            if (!rv) {
                /* Ok, try IPv6 then */
                rv = ares_inet_pton(AF_INET6, start_host, &in6);
                if (!rv) {
                    rv = ARES_EBADSTR;
                    goto out;
                }
                /* was ipv6, add new server */
                s = ares_malloc(sizeof(*s));
                if (!s) {
                    rv = ARES_ENOMEM;
                    goto out;
                }
                s->family = AF_INET6;
                memcpy(&s->addr, &in6, sizeof(struct ares_in6_addr));
            } else {
                /* was ipv4, add new server */
                s = ares_malloc(sizeof(*s));
                if (!s) {
                    rv = ARES_ENOMEM;
                    goto out;
                }
                s->family = AF_INET;
                memcpy(&s->addr, &in4, sizeof(struct in_addr));
            }
            if (s) {
                s->udp_port = use_port ? port : 0;
                s->tcp_port = s->udp_port;
                s->next = NULL;
                if (last) {
                    last->next = s;
                    /* need to move last to maintain the linked list */
                    last = last->next;
                } else {
                    servers = s;
                    last = s;
                }
            }

            /* Set up for next one */
            start_host = ptr + 1;
            cc = 0;
        }
    }

    rv = ares_set_servers_ports(channel, servers);

out:
    if (csv)
        ares_free(csv);
    while (servers) {
        struct ares_addr_port_node* s = servers;
        servers = servers->next;
        ares_free(s);
    }

    return rv;
}

int ares_set_servers_csv(ares_channel channel,
    const char* _csv)
{
    return set_servers_csv(channel, _csv, FALSE);
}

int ares_set_servers_ports_csv(ares_channel channel,
    const char* _csv)
{
    return set_servers_csv(channel, _csv, TRUE);
}
